package edu.amm.fanal.ui;

import java.net.URL;
import java.util.ResourceBundle;

import javafx.application.Platform;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Button;
import javafx.scene.control.ComboBox;
import javafx.scene.control.TextField;
import javafx.scene.layout.AnchorPane;
import edu.amm.fanal.api.Coordinates.CoordinatesType;
import edu.amm.fanal.api.Function;
import edu.amm.fanal.api.NonlinearProblem;
import edu.amm.fanal.methods.GalerkinMethod;
import edu.amm.fanal.methods.LeastSquaresMethod;
import edu.amm.fanal.methods.MomentsMethod;
import edu.amm.fanal.methods.NewtonKantorovichMethod;
import edu.amm.fanal.ui.GraphicsFormController.GraphicData;

/**
 * Контроллер формы ввода данных для настройки методов.
 * 
 * @author Иванов Илья
 * @since 2013-05-19
 *
 */
public class DataInputFormController implements Initializable {
	
	@FXML
	private AnchorPane inputPane;
	@FXML
	private TextField newtonIterations;
	@FXML
	private ComboBox<MomentsMethodType> momentsMethod;
	@FXML
	private ComboBox<CoordinatesType> coordinates;
	@FXML
	private TextField mometsAccuracy;
	@FXML
	private Button solve;
	@FXML
	private Button close;
	
	private Thread solverThread;
	
	public void initialize(URL location, ResourceBundle resources) {
		fillMomentsMethod();
		fillCoordinates();
		
		solve.setOnAction(new EventHandler<ActionEvent>() {
			
			public void handle(ActionEvent event) {
				solve();
			}
		});
		
		close.setOnAction(new EventHandler<ActionEvent>() {
			
			public void handle(ActionEvent event) {
				close();
			}
		});
	}
	
	private void fillMomentsMethod() {
		momentsMethod.getItems().clear();
		momentsMethod.getItems().addAll(MomentsMethodType.values());
		momentsMethod.getSelectionModel().select(MomentsMethodType.GALERKIN);
	}
	
	private void fillCoordinates() {
		coordinates.getItems().clear();
		coordinates.getItems().addAll(CoordinatesType.values());
		coordinates.getSelectionModel().select(CoordinatesType.SINE);
	}
	
	private void solve() {
		int iterations = -1;
		try {
			iterations = Integer.valueOf(newtonIterations.getText());
		} catch (NumberFormatException e) {}
		if (iterations < 1) {
			newtonIterations.selectAll();
			newtonIterations.requestFocus();
			return;
		}
		
		double accuracy = -1;
		try {
			accuracy = Double.valueOf(mometsAccuracy.getText());
		} catch (NumberFormatException e) {}
		if (accuracy < 0) {
			mometsAccuracy.selectAll();
			mometsAccuracy.requestFocus();
			return;
		}
		
		MomentsMethodType moments = momentsMethod.getSelectionModel().getSelectedItem();
		CoordinatesType coordinates = this.coordinates.getSelectionModel().getSelectedItem();
		runSolverAsync(iterations, moments, coordinates, accuracy);
	}
	
	private void runSolverAsync(int iterations, MomentsMethodType momentsType,
		CoordinatesType coordinates, double accuracy) {
		
		setInputEnabled(false);
		
		MomentsMethod moments = momentsType.instantiate(coordinates);
		moments.setAccuracy(accuracy);
		
		final NewtonKantorovichMethod newton = new NewtonKantorovichMethod(moments);
		newton.setInitialApproximation(INITIAL_APPROXIMATION);
		newton.setIterationsCount(iterations);
		
		solverThread = new Thread(new Runnable() {
			
			public void run() {
				final Function solution = newton.solve(EXAMPLE);
				
				solverThread = null;
				Platform.runLater(new Runnable() {
					
					public void run() {
						solverCallback(solution);
					}
				});
			}
		}, "Solver");
		solverThread.setDaemon(true);
		solverThread.start();
	}
	
	private void solverCallback(Function solution) {
		setInputEnabled(true);
		
		GraphicData data = new GraphicData(solution, EXAMPLE.getSolution(),
			EXAMPLE, SEGMENTS_COUNT);
		SolverApplication.getApplication().showGraphic(data);
	}
	
	public void close() {
		stopCalculations();
		SolverApplication.getApplication().closeDataInputForm();
	}
	
	public void stopCalculations() {
		if (solverThread != null) {
			solverThread.stop();
			solverThread = null;
			setInputEnabled(true);
		}		
	}
	
	private void setInputEnabled(boolean enabled) {
		boolean disabled = !enabled;
		inputPane.disableProperty().set(disabled);
		solve.disableProperty().set(disabled);
	}
	
	private enum MomentsMethodType {
		
		GALERKIN {
			
			public MomentsMethod instantiate(CoordinatesType coordinatesType) {
				return new GalerkinMethod(coordinatesType);
			}
			
			public String toString() {
				return GalerkinMethod.getMethodName();
			}
		},
		
		LEAST_SQUARES {
			
			public MomentsMethod instantiate(CoordinatesType coordinatesType) {
				return new LeastSquaresMethod(coordinatesType);
			}
			
			public String toString() {
				return LeastSquaresMethod.getMethodName();
			}
		};
		
		public abstract MomentsMethod instantiate(CoordinatesType coordinatesType);
	}
	
	private static final NonlinearProblem EXAMPLE = SolverApplication.getExample();
	
	private static final Function INITIAL_APPROXIMATION = new Function(NonlinearProblem.SOLUTION_DIM) {
		
		private Function derivative = new Function(getDim()) {
			
			protected double getValue(double... x) {
				return -1;
			}
		};
		
		protected double getValue(double... x) {
			return 1 - x[0];
		}
		
		protected Function getDerivative(int variableNumder) {
			return derivative;
		}
	};
	
	private static final int SEGMENTS_COUNT = 100;
}